#-*- coding:utf8 -*-

import socket
import os
import struct
import threading
import time
import sys
from netaddr import IPNetwork,IPAddress
from ctypes import *

#host = "172.xx.xx.xx"
host = "192.168.119.128"
# 目标子网
# subnet = "192.168.1.0/24"
if len(sys.argv) == 1:# 扫描当前IP网段存在的主机
     subnet = "192.168.119.0/24"  #subnet = "172.19.65.0/24"
else:
    subnet = sys.argv[1]
magic_message = "PYTHONRULES!"

# 批量发送UDP数据包
def udp_sender(subnet, magic_message):
    time.sleep(5)
    sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 建立一个socket对象

    for ip in IPNetwork(subnet):
        try:
            sender.sendto(magic_message, ("%s" % ip, 65212)) # 尝试发送magic_message消息到子网
        except:
            pass


class IP(Structure):                        # ip头定义
    _fields_ = [
        ("ihl",             c_ubyte, 4),    #头长度
        ("version",         c_ubyte, 4),    #版本
        ("tos",             c_ubyte),       #服务类型
        ("len",             c_ushort),      #数据包总长度
        ("id",              c_ushort),      #标识符
        ("offset",          c_ushort),      #片偏移
        ("ttl",             c_ubyte),       #生存时间
        ("protocol_num",    c_ubyte),       #协议数字,应该是协议类型,这里用数字来代表时哪个协议,下面构造函数有设置映射表
        ("sum",             c_ushort),      #头部校验和
        ("src",             c_uint32),      #源ip地址
        ("dst",             c_uint32)       #目的ip地址
    ]

    # __new__(cls, *args, **kwargs)  创建对象时调用，返回当前对象的一个实例;注意：这里的第一个参数是cls即class本身
    def __new__(self, socket_buffer=None):
        return  self.from_buffer_copy(socket_buffer)

    # __init__(self, *args, **kwargs) 创建完对象后调用，对当前对象的实例的一些初始化
    def __init__(self, socket_buffer=None):

        self.protocol_map = {1:"ICMP", 6:"TCP", 17:"UDP"}# 协议字段与协议名称的对应

        self.src_address = socket.inet_ntoa(struct.pack("<L", self.src))
        self.dst_address = socket.inet_ntoa(struct.pack("<L", self.dst))

        try:# 协议类型
            self.protocol = self.protocol_map[self.protocol_num]
        except:
            self.protocol = str(self.protocol_num)


class ICMP(Structure):
    #
    _fields_ = [
        ("type",            c_ubyte),       #类型
        ("code",            c_ubyte),       #代码值
        ("checksum",        c_ubyte),       #头部校验和
        ("unused",          c_ubyte),       #未使用
        ("next_hop_mtu",    c_ubyte)        #下一跳的MTU
    ]

    def __new__(self, socket_buffer):
        return self.from_buffer_copy(socket_buffer)

    def __init__(self, socket_buffer):
        pass

if  os.name == "nt":
    socket_protocol = socket.IPPROTO_IP
else:
    socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)   #raw的中文是生的意思,大概就是原始套接字的意思吧
sniffer.bind((host, 0))
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)# 设置在捕获的数据包中包含IP头

if os.name == "nt":# 在Windows平台上,我们需要设置IOCTL以启用混杂模式
    sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# 开启多线程发送udp数据包
t = threading.Thread(target=udp_sender, args=(subnet, magic_message))
t.start()

try:
    while True:
        raw_buffer =  sniffer.recvfrom(65565)[0] # 读取数据包
        ip_header = IP(raw_buffer[0:20]) # 将缓冲区的前20个字节按IP头进行解析
        # 输出协议和通信双方IP地址 print  "Protocol: %s %s ->  %s" % (ip_header.protocol, ip_header.src_address, ip_header.dst_address)
        if ip_header.protocol == "ICMP":
            # 计算ICMP包的起始位置,并获取ICMP包的数据
            offset = ip_header.ihl * 4
            buf = raw_buffer[offset:offset+sizeof(ICMP)]

            icmp_header = ICMP(buf) # 解析ICMP数据

            #print "ICMP -> Type: %d Code: %d" % (icmp_header.type, icmp_header.code)
            # 检查类型和代码值是否都为3
            if icmp_header.type == 3 and icmp_header.code == 3:
                if IPAddress(ip_header.src_address) in IPNetwork(subnet):
                    if raw_buffer[len(raw_buffer) - len(magic_message):] == magic_message:
                        print("Host Up: %s" % ip_header.src_address)
except  KeyboardInterrupt:
    if os.name == "nt": # 如果运行再Windows上,关闭混杂模式
        sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)